package dti;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;

import common.BitManager;
import common.Constants;
import common.IndexWordUtil;
import common.StopWordFile;
import common.WordHistogram;

/**
 * Main algorithm class. Parses all input files, pulling word tokens from the
 * input based on break characters. Also strips out stop words and processes
 * remaining words. Processed words are written to a BitManager and saved to
 * disk.
 *  
 * @author Zachary M. Allen
 * @author Andrew Bernard
 */
public class DTIIndex {
  /** List of File objects */
  private List filesToIndex = null;

  private IndexWordUtil wordUtil;  

  /** File object representing stop word file */
  private StopWordFile stopWordFile = null;

  private char[] breakChars;
  private int corpusSize = 0;
  
  public DTIIndex(Options options) throws IOException {
    filesToIndex = options.getFilesToIndex();
    breakChars = options.getBreakChars();
    stopWordFile = new StopWordFile(options.getStopWordFile(), breakChars);
    wordUtil = new IndexWordUtil(breakChars);
  }

  /**
   * This is the main algorithm method. It parses the input and creates the
   * index file.
   * 
   * @throws IOException Thrown if an error reading from input occurs
   */
  public String write() throws IOException {
    BitManager bm = new BitManager();

    writeDateTime(bm);
    writeBreakChars(bm);    
    writeStopFileInto(bm);
    bm.addBits(filesToIndex.size(), Constants.NUM_INPUT_FILES_BITS);
    writeFileData(bm);
    
    return bm.save(Constants.INDEX_FILE);
  }
  
  private void writeFileData(BitManager bm) 
  throws FileNotFoundException, IOException {
    Vocabulary vocab = new Vocabulary();
    for (int i = 0; i < filesToIndex.size(); i++) {
      File indexedFile = (File) (filesToIndex.get(i));
      WordHistogram histogram = new WordHistogram(breakChars, stopWordFile, indexedFile);
      vocab.addWordMap(indexedFile.getAbsolutePath(), histogram.getHistogram());
    }
    vocab.createMasterList();
    vocab.pruneVocabList(Constants.PRUNE_THRESHOLD, 
                         Constants.PRUNE_UPPER_PCT, 
                         Constants.PRUNE_LOWER_PCT, 
                         Constants.MAX_INDEXED_TERMS_PER_FILE);
    
    for (int i = 0; i < filesToIndex.size(); i++) {      
      File indexedFile = (File) (filesToIndex.get(i));
      WordHistogram histogram = new WordHistogram(breakChars, stopWordFile, indexedFile);
      corpusSize += indexedFile.length();
      
      // Add file name length and file name
      String filePath = indexedFile.getAbsolutePath();
      bm.addBits(filePath.length(), Constants.FILE_LENGTH_BITS);
      writeString(bm, filePath);

      // Add # chars in file
      bm.addBits(indexedFile.length(), Constants.FILE_CHARS_BITS);

      // Add # words in file
      bm.addBits(histogram.getTotalWordCount(), Constants.FILE_WORDS_BITS);

      //Add terms being indexed for this file
      String terms = vocab.getTermString(filePath);
      bm.addBits(terms.length(), Constants.FILE_TERMS_BITS);
      writeString(bm, terms);
    }
  }
  
  private void writeString(BitManager bm, String string) {
    for(int t = 0; t < string.length(); t++) {
      bm.addBits(string.charAt(t), Constants.BITS_PER_ASCII_BYTE);
    }
  }
  
  private void writeDateTime(BitManager bm) {
    GregorianCalendar gc = new GregorianCalendar();
    gc.setTimeInMillis(System.currentTimeMillis());
    bm.addBits((1 + gc.get(Calendar.MONTH)), Constants.MONTH_BITS);
    bm.addBits(gc.get(Calendar.DATE), Constants.DATE_BITS);
    bm.addBits(gc.get(Calendar.YEAR), Constants.YEAR_BITS);
    bm.addBits(gc.get(Calendar.HOUR_OF_DAY), Constants.HOUR_BITS);
    bm.addBits(gc.get(Calendar.MINUTE), Constants.MINUTE_BITS);
    bm.addBits(gc.get(Calendar.SECOND), Constants.SECOND_BITS);
  }
  
  private void writeBreakChars(BitManager bm) throws IOException {
    for (int j = Constants.FIRST_CUSTOM_BC; j <= Constants.LAST_CUSTOM_BC; j++) {
      if (wordUtil.isBreakCharacter(j)) {
        bm.addBits(1, 1);
      } else {
        bm.addBits(0, 1);
      }
    }
  }
  
  private void writeStopFileInto(BitManager bm) {
    if (stopWordFile == null) {
      bm.addBits(0, Constants.FILE_LENGTH_BITS);
    } 
    else {
      String stopWordFilePath = stopWordFile.getAbsolutePath();
      bm.addBits(stopWordFilePath.length(), Constants.FILE_LENGTH_BITS);
      for (int k = 0; k < stopWordFilePath.length(); k++) {
        bm.addBits(stopWordFilePath.charAt(k), Constants.BITS_PER_ASCII_BYTE);
      }
    }
  }
  
  public double getIndexCorpusRatio() {
    File indexFile = new File(Constants.INDEX_FILE);
    if(corpusSize > 0)
      return (double)indexFile.length() / (double)corpusSize;
    else
      return 0;
  }
}